package com.damai.util;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

public class FieldUtils {
	 private static Map<String, Field> sFieldCache = new HashMap<String, Field>();

	    private static String getKey(Class<?> cls, String fieldName) {
	        StringBuilder sb = new StringBuilder();
	        sb.append(cls.toString()).append("#").append(fieldName);
	        return sb.toString();
	    }

	    private static Field getField(Class<?> cls, String fieldName, final boolean forceAccess) {

	        String key = getKey(cls, fieldName);
	        Field cachedField;
	        synchronized (sFieldCache) {
	            cachedField = sFieldCache.get(key);
	        }
	        if (cachedField != null) {
	            if (forceAccess && !cachedField.isAccessible()) {
	                cachedField.setAccessible(true);
	            }
	            return cachedField;
	        }

	        // check up the superclass hierarchy
	        for (Class<?> acls = cls; acls != null; acls = acls.getSuperclass()) {
	            try {
	                final Field field = acls.getDeclaredField(fieldName);
	                // getDeclaredField checks for non-public scopes as well
	                // and it returns accurate results
	                if (!Modifier.isPublic(field.getModifiers())) {
	                    if (forceAccess) {
	                        field.setAccessible(true);
	                    } else {
	                        continue;
	                    }
	                }
	                synchronized (sFieldCache) {
	                    sFieldCache.put(key, field);
	                }
	                return field;
	            } catch (final NoSuchFieldException ex) { // NOPMD
	                // ignore
	            }
	        }
	        // check the public interface case. This must be manually searched for
	        // incase there is a public supersuperclass field hidden by a private/package
	        // superclass field.
	        Field match = null;
	        for (final Class<?> class1 : Utils.getAllInterfaces(cls)) {
	            try {
	                final Field test = class1.getField(fieldName);
	                match = test;
	            } catch (final NoSuchFieldException ex) { // NOPMD
	                // ignore
	            }
	        }
	        synchronized (sFieldCache) {
	            sFieldCache.put(key, match);
	        }
	        return match;
	    }

	    public static Object readField(final Field field, final Object target, final boolean forceAccess) throws IllegalAccessException {
	        if (forceAccess && !field.isAccessible()) {
	            field.setAccessible(true);
	        } else {
	            MemberUtils.setAccessibleWorkaround(field);
	        }
	        return field.get(target);
	    }


	    public static void writeField(final Field field, final Object target, final Object value, final boolean forceAccess)
	            throws IllegalAccessException {
	        if (forceAccess && !field.isAccessible()) {
	            field.setAccessible(true);
	        } else {
	            MemberUtils.setAccessibleWorkaround(field);
	        }
	        field.set(target, value);
	    }


	    public static Object readField(final Field field, final Object target) throws IllegalAccessException {
	        return readField(field, target, true);
	    }

	    public static Field getField(final Class<?> cls, final String fieldName) {
	        return getField(cls, fieldName, true);
	    }


	    public static Object readField(final Object target, final String fieldName) throws IllegalAccessException {
	        final Class<?> cls = target.getClass();
	        final Field field = getField(cls, fieldName, true);
	        // already forced access above, don't repeat it here:
	        return readField(field, target, false);
	    }

	    public static Object readField(final Object target, final String fieldName, final boolean forceAccess) throws IllegalAccessException {
	        final Class<?> cls = target.getClass();
	        final Field field = getField(cls, fieldName, forceAccess);
	        // already forced access above, don't repeat it here:
	        return readField(field, target, forceAccess);
	    }


	    public static void writeField(final Object target, final String fieldName, final Object value) throws IllegalAccessException {
	        writeField(target, fieldName, value, true);
	    }

	    public static void writeField(final Object target, final String fieldName, final Object value, final boolean forceAccess) throws IllegalAccessException {
	        final Class<?> cls = target.getClass();
	        final Field field = getField(cls, fieldName, true);
	        // already forced access above, don't repeat it here:
	        writeField(field, target, value, forceAccess);
	    }

	    public static void writeField(final Field field, final Object target, final Object value) throws IllegalAccessException {
	        writeField(field, target, value, true);
	    }

	    public static Object readStaticField(final Field field, final boolean forceAccess) throws IllegalAccessException {
	        return readField(field, (Object) null, forceAccess);
	    }

	    public static Object readStaticField(final Class<?> cls, final String fieldName) throws IllegalAccessException {
	        final Field field = getField(cls, fieldName, true);
	        // already forced access above, don't repeat it here:
	        return readStaticField(field, true);
	    }

	    public static void writeStaticField(final Field field, final Object value, final boolean forceAccess) throws IllegalAccessException {
	        writeField(field, (Object) null, value, forceAccess);
	    }


	    public static void writeStaticField(final Class<?> cls, final String fieldName, final Object value) throws IllegalAccessException {
	        final Field field = getField(cls, fieldName, true);
	        // already forced access above, don't repeat it here:
	        writeStaticField(field, value, true);
	    }

	    public static Field getDeclaredField(final Class<?> cls, final String fieldName, final boolean forceAccess) {
	        try {
	            // only consider the specified class by using getDeclaredField()
	            final Field field = cls.getDeclaredField(fieldName);
	            if (!MemberUtils.isAccessible(field)) {
	                if (forceAccess) {
	                    field.setAccessible(true);
	                } else {
	                    return null;
	                }
	            }
	            return field;
	        } catch (final NoSuchFieldException e) { // NOPMD
	            // ignore
	        }
	        return null;
	    }

	    public static void writeDeclaredField(final Object target, final String fieldName, final Object value) throws IllegalAccessException {
	        final Class<?> cls = target.getClass();
	        final Field field = getDeclaredField(cls, fieldName, true);
	        // already forced access above, don't repeat it here:
	        writeField(field, target, value, false);
	    }

}
